001 /*
002 * Copyright 2006 Stephen J. McConnell.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
013 * implied.
014 *
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package net.dpml.transit;
020
021 import java.net.URI;
022 import java.net.URL;
023 import java.net.URLConnection;
024 import java.io.InputStream;
025 import java.io.OutputStream;
026 import java.io.IOException;
027 import java.io.Writer;
028 import java.io.OutputStreamWriter;
029
030 import javax.xml.parsers.DocumentBuilder;
031 import javax.xml.parsers.DocumentBuilderFactory;
032
033 import net.dpml.util.ElementHelper;
034 import net.dpml.transit.info.TransitDirective;
035 import net.dpml.transit.info.CacheDirective;
036 import net.dpml.transit.info.HostDirective;
037 import net.dpml.transit.info.ProxyDirective;
038 import net.dpml.transit.info.LayoutDirective;
039
040 import net.dpml.lang.ValueDirective;
041 import net.dpml.util.DecodingException;
042 import net.dpml.util.Logger;
043
044 import org.xml.sax.ErrorHandler;
045
046 import org.w3c.dom.Element;
047 import org.w3c.dom.Document;
048
049 /**
050 * Utility class supporting the reading of Transit XML configurations.
051 *
052 * @author <a href="http://www.dpml.net">The Digital Product Meta Library</a>
053 * @version 1.0.0
054 */
055 public class TransitBuilder
056 {
057 private static final String XML_HEADER =
058 "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>";
059
060 private static final String NAME = "transit";
061
062 private static final String PUBLIC_ID =
063 "-//DPML//DTD Transit Configuration Version 1.0//EN";
064
065 private static final String SYSTEM_ID =
066 "http://download.dpml.net/dtds/transit_1_0.dtd";
067
068 private static final String RESOURCE =
069 "net/dpml/transit/transit_1_0.dtd";
070
071 private static final String DOCTYPE =
072 "\n<!DOCTYPE "
073 + NAME
074 + " PUBLIC \""
075 + PUBLIC_ID
076 + "\" \""
077 + SYSTEM_ID
078 + "\" >";
079
080 private static final DTD[] DTDS = new DTD[]
081 {
082 new DTD(
083 PUBLIC_ID,
084 SYSTEM_ID,
085 RESOURCE, null )
086 };
087
088 private static final DTDResolver DTD_RESOLVER =
089 new DTDResolver( DTDS, TransitBuilder.class.getClassLoader() );
090
091 private Logger m_logger;
092
093 /**
094 * Creation of a new transit configuration builder.
095 * @param logger the assigned logging channel
096 */
097 public TransitBuilder( Logger logger )
098 {
099 m_logger = logger;
100 }
101
102 /**
103 * Construct a transit configuration from a supplied uri.
104 * @param url the configuration url
105 * @return the transit configuration
106 * @exception Exception if an error occurs during configuration loading
107 */
108 public TransitDirective load( final URL url ) throws Exception
109 {
110 URLConnection connection = url.openConnection();
111 InputStream input = connection.getInputStream();
112
113 final DocumentBuilderFactory factory =
114 DocumentBuilderFactory.newInstance();
115 factory.setValidating( true );
116 factory.setNamespaceAware( true );
117 factory.setExpandEntityReferences( true );
118 DocumentBuilder builder = factory.newDocumentBuilder();
119 builder.setEntityResolver( DTD_RESOLVER );
120 ErrorHandler errors = new SaxMonitor( m_logger );
121 builder.setErrorHandler( errors );
122
123 final Document document = builder.parse( input );
124 final Element root = document.getDocumentElement();
125 return build( root );
126 }
127
128 /**
129 * Write a transit directive to an output stream as XML.
130 * @param directive the directive to externalize
131 * @param output the output stream to write to
132 * @exception IOException if an I/O error occurs
133 */
134 public void write( TransitDirective directive, OutputStream output ) throws IOException
135 {
136 final Writer writer = new OutputStreamWriter( output );
137 try
138 {
139 writer.write( XML_HEADER );
140 writer.write( DOCTYPE );
141
142 CacheDirective cache = directive.getCacheDirective();
143 String cachePath = cache.getCache();
144 String cacheLayout = cache.getCacheLayout();
145 writeHeader( writer, cachePath, cacheLayout );
146
147 ProxyDirective proxy = directive.getProxyDirective();
148 writeProxy( writer, proxy );
149
150 String localPath = cache.getLocal();
151 String localLayout = cache.getLocalLayout();
152 writeLocal( writer, localPath, localLayout );
153
154 HostDirective[] hosts = cache.getHostDirectives();
155 writeHosts( writer, hosts );
156
157 writeFooter( writer );
158 writer.write( "\n" );
159 }
160 finally
161 {
162 writer.flush();
163 writer.close();
164 }
165 }
166
167 //-------------------------------------------------------------
168 // internals supporting XML to directive transformation
169 //-------------------------------------------------------------
170
171 private TransitDirective build( Element root ) throws Exception
172 {
173 String name = root.getTagName();
174 if( !NAME.equals( name ) )
175 {
176 final String error =
177 "Invalid root element name ["
178 + name
179 + "].";
180 throw new IOException( error );
181 }
182
183 String cachePath = ElementHelper.getAttribute( root, "cache" );
184 String cacheLayout = ElementHelper.getAttribute( root, "layout" );
185
186 Element localElement = ElementHelper.getChild( root, "local" );
187 String localPath = ElementHelper.getAttribute( localElement, "path" );
188 String localLayout = ElementHelper.getAttribute( localElement, "layout" );
189
190 Element proxyElement = ElementHelper.getChild( root, "proxy" );
191 ProxyDirective proxy = buildProxyDirective( proxyElement );
192
193 Element hostsElement = ElementHelper.getChild( root, "hosts" );
194 HostDirective[] hosts = buildHosts( hostsElement );
195
196 Element layoutsElement = ElementHelper.getChild( root, "layouts" );
197 LayoutDirective[] layouts = buildLayouts( layoutsElement );
198
199 // handlers TBD
200
201 CacheDirective cache =
202 new CacheDirective(
203 cachePath, cacheLayout, localPath, localLayout,
204 CacheDirective.EMPTY_LAYOUTS, hosts );
205 return new TransitDirective( proxy, cache );
206 }
207
208 private LayoutDirective[] buildLayouts( Element element ) throws Exception
209 {
210 if( null == element )
211 {
212 return null;
213 }
214 else
215 {
216 Element[] layoutElements = ElementHelper.getChildren( element, "layout" );
217 LayoutDirective[] layouts = new LayoutDirective[ layoutElements.length ];
218 for( int i=0; i<layoutElements.length; i++ )
219 {
220 Element elem = layoutElements[i];
221 layouts[i] = buildLayout( elem );
222 }
223 return layouts;
224 }
225 }
226
227 private LayoutDirective buildLayout( Element element ) throws Exception
228 {
229 String id = ElementHelper.getAttribute( element, "id" );
230 String title = ElementHelper.getAttribute( element, "title" );
231 Element codebase = ElementHelper.getChild( element, "codebase" );
232 URI uri = decodeURI( codebase );
233 ValueDirective[] values = getValueDirectives( codebase );
234 return new LayoutDirective( id, title, uri, values );
235 }
236
237 private URI decodeURI( Element element ) throws DecodingException
238 {
239 String uri = ElementHelper.getAttribute( element, "uri" );
240 if( null == uri )
241 {
242 final String error = "Missing uri attribute.";
243 throw new DecodingException( element, error );
244 }
245 else
246 {
247 try
248 {
249 return new URI( uri );
250 }
251 catch( Exception e )
252 {
253 final String error = "Bad uri argument [" + uri + "].";
254 throw new DecodingException( element, error );
255
256 }
257 }
258 }
259
260 private ValueDirective[] getValueDirectives( Element element )
261 {
262 if( null == element )
263 {
264 return null;
265 }
266 else
267 {
268 Element[] valueElements = ElementHelper.getChildren( element, "value" );
269 ValueDirective[] values = new ValueDirective[ valueElements.length ];
270 for( int i=0; i<valueElements.length; i++ )
271 {
272 Element elem = valueElements[i];
273 values[i] = buildValue( elem );
274 }
275 return values;
276 }
277 }
278
279 private ValueDirective buildValue( Element element )
280 {
281 String target = ElementHelper.getAttribute( element, "target" );
282 String method = ElementHelper.getAttribute( element, "method" );
283 String value = ElementHelper.getAttribute( element, "value" );
284 if( value != null )
285 {
286 return new ValueDirective( target, method, value );
287 }
288 else
289 {
290 ValueDirective[] values = getValueDirectives( element );
291 return new ValueDirective( target, method, values );
292 }
293 }
294
295 private ProxyDirective buildProxyDirective( Element element )
296 {
297 if( null == element )
298 {
299 return null;
300 }
301 else
302 {
303 String host = ElementHelper.getAttribute( element, "host" );
304 Element credentialsElement = ElementHelper.getChild( element, "credentials" );
305 String username = getUsername( credentialsElement );
306 char[] password = getPassword( credentialsElement );
307 String[] excludes = buildProxyExcludes( element );
308 return new ProxyDirective( host, excludes, username, password );
309 }
310 }
311
312 private String[] buildProxyExcludes( Element element )
313 {
314 if( null == element )
315 {
316 return null;
317 }
318 else
319 {
320 Element[] elements = ElementHelper.getChildren( element, "exclude" );
321 String[] excludes = new String[ elements.length ];
322 for( int i=0; i<excludes.length; i++ )
323 {
324 Element elem = elements[i];
325 excludes[i] = ElementHelper.getValue( elem );
326 }
327 return excludes;
328 }
329 }
330
331 private HostDirective[] buildHosts( Element element )
332 {
333 Element[] elements = ElementHelper.getChildren( element, "host" );
334 HostDirective[] hosts = new HostDirective[ elements.length ];
335 for( int i=0; i<hosts.length; i++ )
336 {
337 Element elem = elements[i];
338 String id = ElementHelper.getAttribute( elem, "id" );
339 int priority = Integer.parseInt( ElementHelper.getAttribute( elem, "priority" ) );
340 String url = ElementHelper.getAttribute( elem, "url" );
341 String layout = ElementHelper.getAttribute( elem, "layout" );
342 boolean enabled = ElementHelper.getBooleanAttribute( elem, "enabled" );
343 boolean trusted = ElementHelper.getBooleanAttribute( elem, "trusted" );
344 String index = ElementHelper.getAttribute( elem, "index" );
345 String scheme = ElementHelper.getAttribute( elem, "scheme" );
346 String prompt = ElementHelper.getAttribute( elem, "prompt" );
347 Element credentialsElement = ElementHelper.getChild( elem, "credentials" );
348 String username = getUsername( credentialsElement );
349 char[] password = getPassword( credentialsElement );
350 hosts[i] =
351 new HostDirective(
352 id, priority, url, index, username, password, enabled, trusted,
353 layout, scheme, prompt );
354 }
355 return hosts;
356 }
357
358 private String getUsername( Element element )
359 {
360 if( null == element )
361 {
362 return null;
363 }
364 else
365 {
366 return ElementHelper.getAttribute( element, "username" );
367 }
368 }
369
370 private char[] getPassword( Element element )
371 {
372 if( null == element )
373 {
374 return null;
375 }
376 else
377 {
378 String password = ElementHelper.getAttribute( element, "password" );
379 if( null == password )
380 {
381 return null;
382 }
383 else
384 {
385 return password.toCharArray();
386 }
387 }
388 }
389
390 //-------------------------------------------------------------
391 // internals supporting directive to XML transformation
392 //-------------------------------------------------------------
393
394 private void writeHeader( Writer writer, String cache, String layout ) throws IOException
395 {
396 writer.write( "\n\n<" + NAME + " cache=\"" + cache + "\" layout=\"" + layout + "\">" );
397 }
398
399 private void writeFooter( Writer writer ) throws IOException
400 {
401 writer.write( "\n</" + NAME + ">" );
402 }
403
404 private void writeProxy( Writer writer, ProxyDirective proxy ) throws IOException
405 {
406 if( null != proxy )
407 {
408 String host = proxy.getHost();
409 String username = proxy.getUsername();
410 String password = getPassword( proxy.getPassword() );
411 String[] excludes = proxy.getExcludes();
412
413 boolean credentials = ( ( null != username ) || ( null != password ) );
414
415 if( excludes.length == 0 && ( !credentials ) )
416 {
417 writer.write(
418 "\n <proxy host=\"" + host + "\"/>" );
419 }
420 else
421 {
422 writer.write( "\n <proxy host=\"" + host + "\">" );
423 if( credentials )
424 {
425 writer.write( "\n <credentials" );
426 if( null != username )
427 {
428 writer.write( " username=\"" + username + "\"" );
429 }
430 if( null != password )
431 {
432 writer.write( " password=\"" + password + "\"" );
433 }
434 writer.write( "/>" );
435 }
436 if( excludes.length > 0 )
437 {
438 writer.write( "\n <excludes>" );
439 for( int i=0; i<excludes.length; i++ )
440 {
441 String exclude = excludes[i];
442 writer.write( "\n <exclude>" + exclude + "</exclude>" );
443 }
444 writer.write( "\n </excludes>" );
445 }
446
447 writer.write( "\n </proxy>" );
448 }
449 }
450 }
451
452 private void writeLocal( Writer writer, String path, String layout ) throws IOException
453 {
454 writer.write( "\n <local path=\"" + path + "\" layout=\"" + layout + "\"/>" );
455 }
456
457 private void writeHosts( Writer writer, HostDirective[] hosts ) throws IOException
458 {
459 writer.write( "\n <hosts>" );
460 for( int i=0; i<hosts.length; i++ )
461 {
462 HostDirective host = hosts[i];
463 writeHost( writer, host );
464 }
465 writer.write( "\n </hosts>" );
466 }
467
468 private void writeHost( Writer writer, HostDirective host ) throws IOException
469 {
470 String id = host.getID();
471 int priority = host.getPriority();
472 String url = host.getHost();
473 boolean enabled = host.getEnabled();
474 boolean trusted = host.getTrusted();
475 String layout = host.getLayout();
476 String index = host.getIndex();
477 String scheme = host.getScheme();
478 String prompt = host.getPrompt();
479 String username = host.getUsername();
480 String password = getPassword( host.getPassword() );
481 boolean credentials = ( ( null != username ) || ( null != password ) );
482
483 writer.write( "\n <host id=\"" + id + "\" priority=\"" + priority + "\" url=\"" + url + "\"" );
484 if( !enabled )
485 {
486 writer.write( " enabled=\"false\"" );
487 }
488 if( trusted )
489 {
490 writer.write( " trusted=\"true\"" );
491 }
492 if( null != layout )
493 {
494 writer.write( " layout=\"" + layout + "\"" );
495 }
496 if( null != index )
497 {
498 writer.write( " index=\"" + index + "\"" );
499 }
500 if( ( null != scheme ) && !scheme.equals( "" ) )
501 {
502 writer.write( " scheme=\"" + scheme + "\"" );
503 }
504 if( ( null != prompt ) && !prompt.equals( "" ) )
505 {
506 writer.write( " prompt=\"" + prompt + "\"" );
507 }
508 if( credentials )
509 {
510 writer.write( "\n <credentials" );
511 if( null != username )
512 {
513 writer.write( " username=\"" + username + "\"" );
514 }
515 if( null != password )
516 {
517 writer.write( " password=\"" + password + "\"" );
518 }
519 writer.write( "/>" );
520 writer.write( "\n </host>" );
521 }
522 else
523 {
524 writer.write( "/>" );
525 }
526 }
527
528 private String getPassword( char[] password )
529 {
530 if( null == password )
531 {
532 return null;
533 }
534 else
535 {
536 return new String( password );
537 }
538 }
539 }